package org.springframework.boot.netty.handler;

import com.google.protobuf.Message;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.netty.NettyProperties;
import org.springframework.boot.autoconfigure.netty.enums.StartUpMode;
import org.springframework.boot.netty.codec.ProtoBufCodecBean;
import org.springframework.boot.netty.codec.proto.SpeakMessage;
import org.springframework.boot.netty.service.UserDetailService;
import org.springframework.boot.netty.support.JsonUtil;
import org.springframework.boot.netty.webSocketHeart.PingPong;
import org.springframework.context.ApplicationContext;

import java.net.InetSocketAddress;

/**
 * @Description: 用于检测channel的心跳handler 
 * 				 继承ChannelInboundHandlerAdapter，从而不需要实现channelRead0方法
 */
public class HeartBeatHandler extends ChannelInboundHandlerAdapter {

	private ApplicationContext applicationContext;

	private NettyProperties nettyProperties;

	private UserDetailService userDetailService;

	private int allowTimeOutCount = 3;

	private final static Logger logger = LoggerFactory.getLogger(HeartBeatHandler.class);

	public ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
		this.nettyProperties = this.applicationContext.getBean(NettyProperties.class);
		this.userDetailService = this.applicationContext.getBean(UserDetailService.class);
		allowTimeOutCount = this.nettyProperties.getAllowClientTimeOutCount();
	}


	/**
	 * 服务端响应机制
	 * @param ctx
	 * @param msg
	 * @throws Exception
	 */
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

		this.allowTimeOutCount = this.nettyProperties.getAllowClientTimeOutCount();

		if(this.nettyProperties.getStartUpMode().equals(StartUpMode.SERVER)){
			Message message = ((ProtoBufCodecBean) msg).getMessage();
			if(SpeakMessage.class.isAssignableFrom(message.getClass())) {
				sendToClientAsk(ctx);
				// 释放内存
				ReferenceCountUtil.release(msg);
			}
		}

		if(this.nettyProperties.getStartUpMode().equals(StartUpMode.CLINET)){
			Message message = ((ProtoBufCodecBean) msg).getMessage();
			if(SpeakMessage.class.isAssignableFrom(message.getClass())) {
				sendToServerAsk(ctx);
				// 释放内存
				ReferenceCountUtil.release(msg);
			}
		}

		if(this.nettyProperties.getStartUpMode().equals(StartUpMode.WEBSOCKET)){
			if(msg instanceof PingPong) {
				sendWebSocketClientAsk(ctx);
			}
		}

		//业务消息交给其他handler
		super.channelRead(ctx, msg);
	}


	/**
	 * socket 模式下采用服务端主动向客户端发送ping
	 * websocket模式下，采用客户端主动上报
	 * @param ctx
	 * @param evt
	 * @throws Exception
	 */
	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
		
		// 判断evt是否是IdleStateEvent（用于触发用户事件，包含 读空闲/写空闲/读写空闲 ）
		if (evt instanceof IdleStateEvent) {
			IdleStateEvent event = (IdleStateEvent)evt;		// 强制类型转换
			if (event.state() == IdleState.READER_IDLE) {
				// 读空闲，客户端很久没有向channel写数据。超过预设次数，直接关闭无用channel
				allowTimeOutCount = allowTimeOutCount -1;
				if(allowTimeOutCount<=0){
					Channel channel = ctx.channel();
					// 关闭无用的channel，以防资源浪费
					channel.close();
				}
				// 读空闲，客户端很久没有向channel写数据，socket模式下向客户端发送ping，看看是否在线
				if(this.nettyProperties.getStartUpMode().equals(StartUpMode.SERVER)){
					sendToClientAsk(ctx);
				}

			} else if (event.state() == IdleState.WRITER_IDLE) {
				logger.debug("进入写空闲...");
			} else if (event.state() == IdleState.ALL_IDLE) {
				logger.debug("进入读写空闲...");
			}
		}
		
	}

	//服务端使用
	private void sendToClientAsk(ChannelHandlerContext ctx){
		// 获取客户端传输过来的消息，如果是心跳包，则回复pong
			Channel channel = ctx.channel();
			InetSocketAddress socketAddress = (InetSocketAddress)channel.localAddress();
			String clientIp = socketAddress.getAddress().getHostAddress();
			ProtoBufCodecBean protoBufCodecBean = new ProtoBufCodecBean();
			SpeakMessage ping = SpeakMessage.newBuilder()
				.setText("ping")
				.setId(clientIp)
				.build();
			protoBufCodecBean.setMessage(ping);
		    ctx.writeAndFlush(protoBufCodecBean);

	}

	//服务端使用
	private void sendWebSocketClientAsk(ChannelHandlerContext ctx){
		// 获取客户端传输过来的消息，如果是心跳包，则回复pong
		Channel channel = ctx.channel();
		InetSocketAddress socketAddress = (InetSocketAddress)channel.localAddress();
		String clientIp = socketAddress.getAddress().getHostAddress();
		PingPong pingPong = new PingPong();
		pingPong.setPingPongIp(clientIp);
		pingPong.setPingPongAck("pong");
		ctx.writeAndFlush(new TextWebSocketFrame(JsonUtil.obj2StringPretty(pingPong)));

	}

	//客户端端使用
	private void sendToServerAsk(ChannelHandlerContext ctx){
		//获取服务端传输过来的消息，如果是心跳包，则回复pong
		io.netty.channel.Channel channel = ctx.channel();
		InetSocketAddress socketAddress = (InetSocketAddress) channel.remoteAddress();
		String clientIp = socketAddress.getAddress().getHostAddress();
		ProtoBufCodecBean protoBufCodecBean = new ProtoBufCodecBean();
		SpeakMessage pong = SpeakMessage.newBuilder()
				.setText("pong")
				.setId(clientIp)
				.build();
		protoBufCodecBean.setMessage(pong);
		ctx.writeAndFlush(protoBufCodecBean);
	}

}
